﻿// Variable group is used to only send data to those in your game room
var group = null;
var masterHub = null;

// Stores the player list/slots
var gameRoomModel = null;
// User Profile ID of this client
var playerId = null;

// Initalizing function that is called with id of gameroom as parameter.
function initializeFimbulfamb(id, hub, model, idOfPlayer) {
    group = id;
    masterHub = hub;
    gameRoomModel = model;
    playerId = idOfPlayer;

    // ------------------------------------------------------------
    // Add Fimbulfamb specific elements to gameRoomModel.
    // ------------------------------------------------------------

    gameRoomModel.fambi = ko.observable(0);
    gameRoomModel.nextToAct = ko.observable(0);
    // Create an empty array for scores. _.times function handles this easily.
    gameRoomModel.playerScores = ko.observableArray(_.times((gameRoomModel.slots().length), function () { return 0; }));
    // Initialize to 24 points by default.
    gameRoomModel.target = ko.observable(24);
    // Use a computed value to ensure it is within range.
    gameRoomModel.targetScore = ko.computed(function (val) {
        return Math.max(Math.min(24, gameRoomModel.target()), 1);
    }, this);
    // Just a flag to display the correct button.
    gameRoomModel.allCorrect = ko.observable(false);
    // round is the the model to store round state and pass between clients.
    var round = new ffRound(0, gameRoomModel.slots().length, 0);
    gameRoomModel.round = ko.observable(round);
    // And observables to represent the round data in UI.
    gameRoomModel.explanations = ko.observableArray(round.playerExplanations);
    gameRoomModel.guesses = ko.observableArray(round.playerGuesses);
    gameRoomModel.explanationsDisplay = ko.observableArray([]);

    // This will evaluate if all players have submitted an explanation.
    gameRoomModel.allExplained = ko.computed(function (val) {
        var count = 0;
        // Count all explanations that have been submitted.
        var exps = _.each(gameRoomModel.explanations(), function (e) {
            if (e.explanation != '') {
                count++;
            }
        });
        return count >= (gameRoomModel.players().length - 1);
    }, this);
    // Settings returns all setting related observables to serialize for sending over network.
    // updateSettings will have to handle the inverse process.
    gameRoomModel.settings = ko.computed(function (val) {
        var s = {};
        s.target = gameRoomModel.target();
        return s;
    }, this);

    // ------------------------------------------------------------
    // Added methods for GameRoomViewModel specific to fimbulfamb.
    // ------------------------------------------------------------

    gameRoomModel.getExpText = function (e) {
        if (e != null) {
            return e.exp;
        } else {
            return "";
        }
    }
    gameRoomModel.getFambiName = function () {
        return _.findWhere(gameRoomModel.players(), {'slotNumber': gameRoomModel.round().fambi}).userName;
    }

    // Click handler for guesses. Updates selected state of guess.
    gameRoomModel.selectGuess = function (exp) {
        var exps = gameRoomModel.explanationsDisplay();
        _.each(exps, function (e) { e.sel = false });
        exp.sel = true;
        // Brute force hack to update UI.
        gameRoomModel.explanationsDisplay([]);
        gameRoomModel.explanationsDisplay(exps);
    };
    // Click handler for submitGuess button.
    gameRoomModel.submitGuess = function () {
        var guesses = gameRoomModel.round().playerGuesses;
        var exps = gameRoomModel.explanationsDisplay();
        // Find the guessed explanation.
        var guessed = ko.utils.arrayFirst(gameRoomModel.explanationsDisplay(), function (exp) {
            return exp.sel;
        });
        if (guessed == null) {
            alert('Ekkert valið');
        } else {
            // Update current user guess.
            guesses[gameRoomModel.slotNumber] = guessed.pos;
            // And reset selection.
            guessed.sel = false;
            // Find out who is next to act.
            sN = gameRoomModel.getNextPlayer(gameRoomModel.nextToAct());
            if (sN == gameRoomModel.round().fambi) {
                // Send round into scoring state since all have finished guessing.
                gameRoomModel.round().state = 'Scoring';
                gameRoomModel.round().nextToAct = sN;
            } else {
                gameRoomModel.round().nextToAct = sN;
            }
            masterHub.server.updateRound(group, JSON.stringify(gameRoomModel.round()));
        }
    };
    // Click handler for All correct button if all players submitted correct explanation to word.
    gameRoomModel.finishRound = function () {
        gameRoomModel.allCorrect(false);
        // Send round into scoring state since all have finished guessing.
        gameRoomModel.round().state = 'Scoring';
        // Round should end with nextToAct pointing to fambi.
        gameRoomModel.round().nextToAct = gameRoomModel.round().fambi;
        masterHub.server.updateRound(group, JSON.stringify(gameRoomModel.round()));
    };
    gameRoomModel.getNextPlayer = function (slotNumber) {
        // Find out who is next to act. Really inelegant solution requiring two passes of array.
        var acting = ko.utils.arrayFirst(gameRoomModel.players(), function (p) {
            return p.slotNumber == slotNumber;
        });
        var index = gameRoomModel.players().indexOf(acting);
        do {
            // Move to next, wrapping if needed.
            index++;
            if (index >= gameRoomModel.players().length) {
                index = 0;
            }
            // Loop if the player has a correct explanation.
        } while (_.findWhere(gameRoomModel.round().playerExplanations, { 'slotNumber': gameRoomModel.players()[index].slotNumber }).correct == true);
        acting = gameRoomModel.players()[index];
        return acting.slotNumber;
    }

    // Click handler for button to leave 'scoring' state and move on to next round.
    gameRoomModel.nextRound = function () {
        // round().nextToAct point to current fambi, so next round fambi is nextPlayer.
        var nextFambi = gameRoomModel.round().fambi + 1;
        if (nextFambi >= gameRoomModel.players().length) {
            nextFambi = 0;
        }
        var round = new ffRound(nextFambi, gameRoomModel.slots().length, gameRoomModel.round().deckPosition + 1);
        // Update slot numbers of playerExplanations.
        for (var i = 0; i < gameRoomModel.slots().length; i++) {
            round.playerExplanations[i].slotNumber = gameRoomModel.players()[i].slotNumber;
        }
        // And first to guess on next round is next player after new fambi.
        //        round.nextToAct = gameRoomModel.getNextPlayer(nextFambi);
        gameRoomModel.round(round);
        masterHub.server.updateRound(group, JSON.stringify(round));
    }
    // Send settings to Hub for distributing to all clients.
    gameRoomModel.updateSettings = function () {
        masterHub.server.updateSettings(group, JSON.stringify(gameRoomModel.settings()));
    };
    // Click handler for fambis start guessing button.
    gameRoomModel.startGuessing = function () {
        var round = gameRoomModel.round();
        round.state = "Guessing";
        // And first to guess on next round is next player after new fambi.
        round.nextToAct = gameRoomModel.getNextPlayer(gameRoomModel.round().fambi);
        gameRoomModel.setupForGuesses();
        // Send round to all clients.
        masterHub.server.updateRound(group, JSON.stringify(round));
    };
    // Call hub to load next word from database.
    gameRoomModel.startRound = function () {
        masterHub.server.startRound(group, gameRoomModel.deck[gameRoomModel.round().deckPosition]);
    };
    gameRoomModel.submitExplanation = function () {
        var exp = $("textarea#explanation").val();
        $("textarea#explanation").val('');
        var round = gameRoomModel.round();
        round.playerExplanations[gameRoomModel.slotNumber] = new ffExplanation(gameRoomModel.slotNumber, exp);
        masterHub.server.updateRound(group, JSON.stringify(round));
    };
    // This function creates a list of explanations to guess on.
    gameRoomModel.setupForGuesses = function () {
        var exps = [];
        var pos = 0;
        exps.push({ 'pos': pos++, 'exp': gameRoomModel.round().explanation, 'sel': false });
        _.each(gameRoomModel.explanations(), function (e) {
            if (e.explanation != '' && e.correct != true) {
                exps.push({ 'pos': pos++, 'exp': e.explanation, 'sel': false });
            }
        });
        exps = _.shuffle(exps);
        gameRoomModel.round().explanationsDisplay = exps;
    }

    // This will evaluate if all explanations are correct.
    // Results are used to trigger which button to show to fambi after evaluating player explanations.
    gameRoomModel.areAllCorrect = function () {
        var all = true;
        // Count all explanations that have been submitted.
        _.each(gameRoomModel.players(), function (p) {
            // Fambi has no explanation.
            if (p.slotNumber == gameRoomModel.round().fambi) {
                return;
            }
            var exp = _.findWhere(gameRoomModel.round().playerExplanations, { 'slotNumber': p.slotNumber });
            // If this one is not correct then not all are!
            if (exp.correct == false) {
                all = false;
            }
        });
        return all;
    };

    // Update explanation to reflect checkbox.
    gameRoomModel.setCorrect = function (player, event) {
        var exp = _.findWhere(gameRoomModel.round().playerExplanations, { 'slotNumber': player.slotNumber });
        exp.correct = event.srcElement.checked;
        // Update allCorrect flag. KO observable issue.
        gameRoomModel.allCorrect(gameRoomModel.areAllCorrect());
        // To allow click event to run.
        return true;
    }

    // Go through all round data and calculate scores.
    // Returns array of playerScores.
    gameRoomModel.calculateScores = function () {
        ps = gameRoomModel.playerScores();
        _.each(gameRoomModel.players(), function (p) {
            if (p.slotNumber == gameRoomModel.round().fambi) {
                // Fambi. If noone guessed 0 (pos of correct) then award fambi 2 points.
                if (!_.contains(gameRoomModel.round().playerGuesses, 0)) {
                    ps[p.slotNumber] += 2;
                }
                return;
            }
            // Get player explanation
            var exp = _.findWhere(gameRoomModel.round().playerExplanations, { 'slotNumber': p.slotNumber });
            if (exp.correct) {
                // Correct explanation, award 2 points.
                ps[p.slotNumber] += 2;
                return;
            }
            // Find what player guessed.
            var guessed = _.findWhere(gameRoomModel.round().explanationsDisplay, { 'pos': gameRoomModel.round().playerGuesses[p.slotNumber] }).exp;
            if (guessed == gameRoomModel.round().explanation) {
                // Guessed the right word, 1 point.
                ps[p.slotNumber] += 1;
            }
            // Find pos in expDisplay list to check how many guessed players explanation.
            var pos = _.findWhere(gameRoomModel.round().explanationsDisplay, { 'exp': exp.explanation }).pos;
            // TODO: not count own guesses.
            var otherGuesses = 0;
            _.each(gameRoomModel.players(), function (p2) {
                if ((gameRoomModel.round().playerGuesses[p2.slotNumber] == pos) &&
                    (p.slotNumber != p2.slotNumber)) {
                    // Found a player that guessed p's explanation and is not himself.
                    otherGuesses++;
                }
            });
            ps[p.slotNumber] += otherGuesses;
        });
        return ps;
    }

    // ------------------------------------------------------------
    // Callback functions to receive messages from Hub.
    // ------------------------------------------------------------


    // Recieves an updated list of players and stores it.
    masterHub.client.updatePlayers = function (players) {
        // playerList = $.parseJSON(players);
    };

    // Recieves an updated list of players and stores it.
    masterHub.client.updateRound = function (r) {
        r = $.parseJSON(r);
        gameRoomModel.round(r);
        // Copy all data from json datastructure to observables for KO.
        gameRoomModel.explanations(r.playerExplanations);
        gameRoomModel.guesses(r.playerGuesses);
        gameRoomModel.explanationsDisplay(r.explanationsDisplay);
        gameRoomModel.nextToAct(r.nextToAct);
        if (r.state == 'Starting') {
            // GameRoomModel.fambi is userProfileID of current round fambi. Round.fambi is slotnumber.
            gameRoomModel.fambi(gameRoomModel.players()[r.fambi].userProfileID);
        }
        if (r.state == 'Scoring' && r.calculated == false && (gameRoomModel.slotNumber == gameRoomModel.round().fambi)) {
            ps = gameRoomModel.calculateScores();
            masterHub.server.updateScores(group, JSON.stringify(ps));
            r.calculated = true;
            // Check if we have a winner.
            if (_.max(ps) >= gameRoomModel.targetScore()) {
                var results = _.map(gameRoomModel.players(), function (p) {
                    // Create a 2D array with player IDs and scores.
                    return [p.userProfileID, ps[p.slotNumber]];
                });
                masterHub.server.logResults(group, JSON.stringify(results));
                gameRoomModel.gameRoomState('Finished');
                masterHub.server.endGame(group, gameRoomModel.gameRoomID);
            }
        }
    };

    // Recieves data from another players gameRoomModel.settings(). Restore that data.
    masterHub.client.updateSettings = function (s) {
        s = $.parseJSON(s);
        gameRoomModel.target(s.target);
    };

    masterHub.client.updateScores = function (ps) {
        ps = $.parseJSON(ps);
        // And still forcing KO to update. Friggin IE.
        gameRoomModel.playerScores([]);
        gameRoomModel.playerScores(ps);
    };

    // Start Round callback. Gets the word for the round back.
    masterHub.client.startRound = function (w) {
        // Update round word.
        var word = JSON.parse(w);
        var round = gameRoomModel.round();
        round.state = "Explaining";
        round.word = word.word;
        round.explanation = word.explanation;
        gameRoomModel.round(round);
        // Send round to all clients.
        masterHub.server.updateRound(group, JSON.stringify(round));
    }

    // Start Game function which is called when gameroom is ready and start game button clicked.
    masterHub.client.startGame = function (words) {
        // Set deck of words to model.
        gameRoomModel.deck = JSON.parse(words);
        // Set first player as fambi for first round.
        var round = gameRoomModel.round();
        round.fambi = gameRoomModel.players()[0].slotNumber;
        round.nextToAct = gameRoomModel.players()[1].slotNumber;
        gameRoomModel.round(round);
        gameRoomModel.nextToAct(round.nextToAct);
    }

    // Starts the connection with the Signalr server.
    $.connection.hub.start().done(function () {
    });
}

// ----------------------------------------------------------
// Custom bindings for knockout. Used to draw game board.
// ----------------------------------------------------------

ko.bindingHandlers.ffYAxis = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        // This will be called when the binding is first applied to an element
        // Set up any initial state, event handlers, etc. here
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var count = valueAccessor();
        var height = Math.floor(360 / count);
        $(element).html("");
        for (var i = count ; i > 0; i--) {
            // Add count scoremarkers 
            $(element).append('<li style="height:' + height + 'px;">' + i + '</li>');
        }
    }
};

ko.bindingHandlers.ffXAxis = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var players = valueAccessor();
        var width = Math.floor(100 / players.length);
        $(element).html("");
        _.each(players, function (p) {
            // Add each players name to list. 
            $(element).append('<li style="width:' + width + '%;">' + p.userName + '</li>');
        });
    }
};

ko.bindingHandlers.ffBars = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var players = valueAccessor();
        var width = Math.floor(100 / players.length);
        // Plus one to count for border on marker.
        var pointHeight = Math.floor(360 / viewModel.targetScore()) + 1;
        $(element).html("");
        _.each(players, function (p) {
            var score = viewModel.playerScores()[p.slotNumber];
            var pixels = pointHeight * score + 1;
            // Add count scoremarkers 
            $(element).append('<div class="barContainer" style="width:' + width + '%;"><div class="bar" style="height:' + pixels + 'px"></div></div>');
        });
    }
};

// ------------------------------------------------------------
// Model "classes" for fimbulfamb javascript implementation.
// ------------------------------------------------------------

function ffRound(f, playerCount, d) {
    var self = this;

    self.state = "Starting";
    self.fambi = f;
    self.word = "";
    self.nextToAct = 0;
    self.calculated = false;
    self.deckPosition = d;
    self.playerExplanations = _.times(playerCount, function () {
        return new ffExplanation(0, '');
    });
    self.playerGuesses = _.times(playerCount, function () {
        return null;
    });
    self.explanationsDisplay = {};
}

function ffExplanation(s, e) {
    var self = this;

    self.slotNumber = s;
    self.explanation = e;
    self.correct = false;
    self.equal = false;
    self.equalTo = 0;
}